package eval.host;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.inject.Singleton;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.vm.VirtualMachine;
import eval.deploy.IDemandManager;
import eval.exceptions.CpuLeakAtHostException;
import eval.exceptions.MemLeakAtHostException;

/**
 * It manages the load of hosts from configuration files, their distribution and
 * the generation of new hosts according to the distribution
 * 
 * @author Nicolo' M. Calcavecchia <calcavecchia@elet.polimi.it>
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com> 
 * */
@Singleton
public class HostManager implements IHostManager {

	private static ExperimentConfiguration configuration = ExperimentConfiguration.getSingleton();

	/**
	 * Map holding the various host capacities
	 * */
	private Map<HostCapacityEnum, HostCapacity> hostCapacities = new HashMap<HostCapacityEnum, HostCapacity>();
	private Map<HostCapacityEnum, Double> hostDistribution = new HashMap<HostCapacityEnum, Double>();
	private Map<HostCapacityEnum, Integer> hostStartUpCostUnit = new HashMap<HostCapacityEnum, Integer>();
	private Map<HostCapacityEnum, Integer> hostBAseUpCostUnit = new HashMap<HostCapacityEnum, Integer>();
	/**
	 * Data structure containing the deployed hosts
	 * */

	private List<Host> hosts = new ArrayList<Host>();

	private double totalCpuCapacity = 0;
	private double totalMemCapacity = 0;

	/**
	 * @Constructor
	 */
	public HostManager() {
		loadHostProperties();
	}

	/**
	 * Create Host Capacity
	 * 
	 * @param hostCapacityEnum the host type to create 
	 * @return the new HostCapacity
	 */
	public HostCapacity createNewHostCapacity(HostCapacityEnum hostCapacityEnum) {
		int cpuCapacity = 0;
		int memCapacity = 0;
		int hostBaseEnergyConsumption = 0;
		int hostPowerOnEnergyConsumption = 0;

		switch (hostCapacityEnum) {
		case SMALL:
			cpuCapacity = configuration.smallHostCpuCapacity;
			memCapacity = configuration.smallHostMemCapacity;
			hostBaseEnergyConsumption = configuration.smallhostBaseEnergyConsumption;
			hostPowerOnEnergyConsumption = configuration.smallhostPowerOnConsumption;
			break;
		case MEDIUM:
			cpuCapacity = configuration.mediumHostCpuCapacity;
			memCapacity = configuration.mediumHostMemCapacity;
			hostBaseEnergyConsumption = configuration.mediumhostBaseEnergyConsumption;
			hostPowerOnEnergyConsumption = configuration.mediumhostPowerOnConsumption;
			break;

		case LARGE:
			cpuCapacity = configuration.largeHostCpuCapacity;
			memCapacity = configuration.largeHostMemCapacity;
			hostBaseEnergyConsumption = configuration.largehostBaseEnergyConsumption;
			hostPowerOnEnergyConsumption = configuration.largehostPowerOnConsumption;
			break;

		default:
			throw new IllegalArgumentException("Invalid value for "
					+ HostCapacityEnum.class.getSimpleName() + " enumeration");
		}

		if (cpuCapacity == 0 || memCapacity == 0)
			throw new IllegalArgumentException(
					"Invalid values for host capacity, cpu: " + cpuCapacity
					+ ", mem: " + memCapacity);

		return new HostCapacity(cpuCapacity, memCapacity, hostBaseEnergyConsumption,hostPowerOnEnergyConsumption);

	}

	/**
	 * Find host to open 
	 * 
	 * @return the host to open, null if all hosts are already opens
	 */
	public Host findBestHostToOpen(){
		Host bestHost = null;
		int maxCapcity = 0;
		int CurhostCpu;
		for(Host host : hosts){
			CurhostCpu = host.getHostCapacity().getCpuCapacity();
			if(host.isClose() && CurhostCpu > maxCapcity){
				maxCapcity = CurhostCpu;
				bestHost = host;
			}
		}
		return bestHost;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#InitHostsInCloud()
	 */
	@Override
	public void InitHostsInCloud(){
		Host host = openNewHost(0);
		for (Host curHost : hosts) {
			if(host.getHostId() != curHost.getHostId()){
				curHost.close();
			}
		}	
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getToatalCpuLoadOnCloud()
	 */
	@Override
	public int getToatalCpuLoadOnCloud() {
		int totalLoad = 0;
		for(Host host : hosts) {
			if(host.isOpen())
				totalLoad += host.getCurrentCpuLoad();
		}
		return totalLoad;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#makeHostCandidateForClosing(eval.host.Host, int)
	 */
	@Override
	public void makeHostCandidateForClosing(Host host,int tickRemainingForClosing) {
		host.makeCandidateForClosing(tickRemainingForClosing);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#removeHostFromCandidateForClosing(eval.host.Host)
	 */
	@Override
	public void removeHostFromCandidateForClosing(Host host) {
		host.remCandidateForClosing();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#setHostCurrentLoad(eval.host.Host, int, int)
	 */
	@Override
	public void setHostCurrentLoad(Host host, int cpuLoad, int memLoad) {
		try {
			setHostCurrentCpuLoad(host,cpuLoad);
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			setHostCurrentMemLoad(host,memLoad);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#setHostCurrentCpuLoad(eval.host.Host, int)
	 */
	@Override
	public void setHostCurrentCpuLoad(Host host, int cpuLoad) throws Exception {
		if(host.getHostCapacity().getCpuCapacity()<cpuLoad)
			throw new Exception("setHostCurrentCpuLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+cpuLoad+" Host caps: "+host.getHostCapacity().getCpuCapacity());
		host.setCurrentCpuLoad(cpuLoad);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#setHostCurrentMemLoad(eval.host.Host, int)
	 */
	@Override
	public void setHostCurrentMemLoad(Host host, int memLoad) throws Exception {
		if(host.getHostCapacity().getMemCapacity()<memLoad)
			throw new Exception("setHostCurrentMemLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+memLoad+" Host caps: "+host.getHostCapacity().getMemCapacity());
		host.setCurrentMemLoad(memLoad);		
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#updateHostCurrentLoad(eval.host.Host, int, int)
	 */
	@Override
	public void updateHostCurrentLoad(Host host, int cpuLoad, int memLoad) {
		try {
			updateHostCurrentCpuLoad(host,cpuLoad);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		try {
			updateHostCurrentMemLoad(host,memLoad);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#updateHostCurrentCpuLoad(eval.host.Host, int)
	 */
	@Override
	public void updateHostCurrentCpuLoad(Host host, int cpuLoad) throws CpuLeakAtHostException {
		if(host.getHostCapacity().getCpuCapacity()<cpuLoad+host.getCurrentCpuLoad())
			throw new CpuLeakAtHostException("updateHostCurrentCpuLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+cpuLoad+" after update: "+ cpuLoad+host.getCurrentCpuLoad() +" Host Cpu caps: "+host.getHostCapacity().getMemCapacity());
		host.updateCurrentCpuLoad(cpuLoad);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#updateHostCurrentMemLoad(eval.host.Host, int)
	 */
	@Override
	public void updateHostCurrentMemLoad(Host host, int memLoad) throws MemLeakAtHostException {
		if(host.getHostCapacity().getCpuCapacity()<memLoad+host.getCurrentMemLoad())
			throw new MemLeakAtHostException("updateHostCurrentMemLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Mem req: "+memLoad+" after update: "+ memLoad+host.getCurrentMemLoad() +"Host Mem caps: "+host.getHostCapacity().getMemCapacity());
		host.updateCurrentMemLoad(memLoad);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#removeListOfVmFromHosts(java.util.List)
	 */
	@Override
	public void removeListOfVmFromHosts(List<VirtualMachine> removeList) {
		for(Host host : hosts) {
			host.removeListOfVmFromHost(removeList);
		}
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#addVmToHost(eval.host.Host, eval.vm.VirtualMachine, int, int)
	 */
	@Override
	public void addVmToHost(Host host, VirtualMachine vm, int cpuLoad, int memLoad) {
		host.addVmToHost(vm, cpuLoad, memLoad);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getVmsAtHost(eval.host.Host)
	 */
	@Override
	public List<VirtualMachine> getVmsAtHost(Host host) {
		return host.getVmsAtHost();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getCpuAvailableAtHost(eval.host.Host)
	 */
	@Override
	public int getCpuAvailableAtHost(Host host) {
		return host.getHostCapacity().getCpuCapacity() - host.getCurrentCpuLoad();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getStartUpCostForHost(eval.host.HostCapacityEnum)
	 */
	@Override
	public double getStartUpCostForHost(HostCapacityEnum hostCapacityEnum) {
		return hostStartUpCostUnit.get(hostCapacityEnum);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getLoadCost(int)
	 */
	@Override
	public double getLoadCost(int load) {
		return load*configuration.loadCost;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getIncomeOnLoad(int)
	 */
	@Override
	public double getIncomeOnLoad(int totalCloudCPULoad) {
		return totalCloudCPULoad*configuration.incomeOnLoad;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getToatalMemLoadOnCloud()
	 */
	@Override
	public int getToatalMemLoadOnCloud() {
		int totalMem = 0;
		for(Host host : getOpenHosts()) {
			totalMem += host.getCurrentMemLoad();
		}
		return totalMem;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getBaseCostForHost(eval.host.HostCapacityEnum)
	 */
	@Override
	public double getBaseCostForHost(HostCapacityEnum hostCapacityEnum) {
		return hostBAseUpCostUnit.get(hostCapacityEnum);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#relocateVm(eval.vm.VirtualMachine, eval.host.Host, eval.host.Host, int, int)
	 */
	@Override
	public void relocateVm(VirtualMachine vm, Host fromHost, Host toHost, int cpu, int mem) {
		fromHost.removeVmFromHost(vm);
		toHost.addVmToHost(vm,cpu,mem);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getAllVmAtOpenHosts()
	 */
	@Override
	public List<VirtualMachine> getAllVmAtOpenHosts() {
		List<VirtualMachine> toRet = new ArrayList<VirtualMachine>();
		for(Host host :getOpenHosts()) 
			toRet.addAll(host.getVmsAtHost());
		return toRet;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#rejectVms(eval.vm.VirtualMachine)
	 */
	@Override
	public void rejectVms(VirtualMachine vm) {
		for(Host host : hosts) {
			if(host.isOpen()) {
				host.removeVmFromHost(vm);
			}
		}
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getNumberOfHosts()
	 */
	@Override
	public int getNumberOfHosts() {
		return hosts.size();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getNumberOfOpenHosts()
	 */
	@Override
	public int getNumberOfOpenHosts() {
		int count = 0;
		for(Host host : hosts){
			if(host.isOpen()){
				count++;
			}
		}
		return count;	
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getNumberOfClosedHosts()
	 */
	@Override
	public int getNumberOfClosedHosts() {
		int count = 0;
		for(Host host : hosts){
			if(host.isClose()){
				count++;
			}
		}
		return count;	
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getHostByID(int)
	 */
	@Override
	public Host getHostByID(int hostIdentifier) {
		for (Host host : hosts){
			if(host.getHostId() == hostIdentifier)
				return host;
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getHosts()
	 */
	@Override
	public List<Host> getHosts() {
		return hosts;
	}


	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getOpenHosts()
	 */
	@Override
	public List<Host> getOpenHosts() {
		List<Host> openHosts =  new ArrayList<Host>();
		for(Host host : hosts){
			if(host.isOpen()){
				openHosts.add(host);
			}
		}
		return openHosts;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getCpuCapacityForHost(eval.host.Host)
	 */
	@Override
	public int getCpuCapacityForHost(Host host) {
		return (int) hostCapacities.get(host.getHostCapacityEnum())
				.getCpuCapacity();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getMemCapacityForHost(eval.host.Host)
	 */
	@Override
	public int getMemCapacityForHost(Host host) {
		return (int) hostCapacities.get(host.getHostCapacityEnum())
				.getMemCapacity();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#printAllHosts()
	 */
	@Override
	public void printAllHosts() {
		for (HostCapacityEnum hostCapacity : HostCapacityEnum.values()) {

			System.out.print("HOST " + hostCapacity.name() + " ["
					+ hostCapacities.get(hostCapacity) + "]");

			List<Integer> hostFound = new ArrayList<Integer>();
			for (int i = 0; i < hosts.size(); i++) {
				if (hosts.get(i).equals(hostCapacity))
					hostFound.add(i);
			}
			System.out.println(" ->  " + hostFound);
		}
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getTotalCpuCapacity()
	 */
	@Override
	public int getTotalCpuCapacity() {
		return (int) totalCpuCapacity;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getTotalMemCapacity()
	 */
	@Override
	public int getTotalMemCapacity() {
		return (int) totalMemCapacity;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#addHosts(java.util.List)
	 */
	@Override
	public void addHosts(List<Host> hosts) {
		for (Host host : hosts) {
			addHost(host);
		}	
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#addHost(eval.host.Host)
	 */
	@Override
	public void addHost(Host host) {
		hosts.add(host);
		totalCpuCapacity += hostCapacities.get(host.getHostCapacityEnum())
				.getCpuCapacity();
		totalMemCapacity += hostCapacities.get(host.getHostCapacityEnum())
				.getMemCapacity();
		host.close();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getHostDistribution()
	 */
	@Override
	public Map<HostCapacityEnum, Double> getHostDistribution() {
		return new HashMap<HostCapacityEnum, Double>(hostDistribution);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getCpuCapacityOfType(eval.host.HostCapacityEnum)
	 */
	@Override
	public int getCpuCapacityOfType(HostCapacityEnum hostType) {
		return (int) hostCapacities.get(hostType).getCpuCapacity();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getMemCapacityOfType(eval.host.HostCapacityEnum)
	 */
	@Override
	public int getMemCapacityOfType(HostCapacityEnum hostType) {
		return (int) hostCapacities.get(hostType).getMemCapacity();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#getHostCapacity(eval.host.HostCapacityEnum)
	 */
	@Override
	public HostCapacity getHostCapacity(HostCapacityEnum hostType) {
		return hostCapacities.get(hostType);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#closeHost(eval.host.Host)
	 */
	@Override
	public void closeHost(Host host) {
		host.close();
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#updateCurrentHostDemand(eval.deploy.IDemandManager, eval.host.Host, java.util.List, int)
	 */
	@Override
	public void updateCurrentHostDemand(IDemandManager demandManager,Host host , List<VirtualMachine> vmsInHost, int tick){
		int Demand = 0;
		for (VirtualMachine vms : vmsInHost) {
			Demand += demandManager.getRealDemand(vms.getIdentifier(),tick).getCPUDemandAtTick(tick);
		}
		host.setCurrentCpuDemand(Demand);
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#openNewHostWithMinCpu(double, int)
	 */
	@Override
	public boolean openNewHostWithMinCpu(double minCpuNeeded,int tick) {
		if(configuration.totalNumberOfHosts <= getNumberOfOpenHosts())
			return false;
		HostCapacityEnum typeofHost = null;
		for(Map.Entry<HostCapacityEnum, HostCapacity> entry : hostCapacities.entrySet()) {
			if(entry.getValue().getCpuCapacity()>= minCpuNeeded) {
				typeofHost = entry.getKey();
				break;
			}
		}
		if(typeofHost != null) {
			for(Host host: hosts) {
				if(host.isClose() && host.getHostCapacityEnum()==typeofHost) {
					host.open(tick);
					return true;
				}
			}
		}
		double cpuSatisfied = 0;
		boolean flag = false;
		Host host;
		do {
			host = openNewHost(tick);
			if(host != null) {
				cpuSatisfied += getCpuCapacityForHost(host); 
				flag = true;
			} else
				return flag;
		}while(cpuSatisfied<minCpuNeeded);
		return flag;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#openNewHost(int)
	 */
	@Override
	public Host openNewHost(int tick) {
		if(configuration.totalNumberOfHosts <= getNumberOfOpenHosts()){
			return null;
		}
		Host host = findBestHostToOpen();
		if(host!=null && host.isClose()){
			host.open(tick);
			return host;
		}
		else if(host == null)
		{System.out.println("ERROR(#1): cant open new Host.");}
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.host.IHostManager#openHost(eval.host.Host, int)
	 */
	@Override
	public void openHost(Host host,int tick) {
		host.open(tick);
	}

	/*
	 * Initial Hosts Properties
	 */
	private void loadHostProperties() {
		double probabilitySum = 0;
		for (HostCapacityEnum value : HostCapacityEnum.values()) {
			hostCapacities.put(value, createNewHostCapacity(value));
			double probability = getHostDistribution(value);
			hostDistribution.put(value, probability);
			probabilitySum += probability;
			hostStartUpCostUnit.put(value, configuration.getStartUpUnitCost(value));
			hostBAseUpCostUnit.put(value,configuration.getBAseUnitCost(value));
		}
		if (probabilitySum != 1)
			throw new IllegalArgumentException(
					"The sum of probabilities for host distribution is not 1!");

	}

	/*
	 * Get Host Distribution
	 * */
	private double getHostDistribution(HostCapacityEnum value) {
		switch (value) {
		case SMALL:
			return new Double(configuration.smallHostProbability);
		case MEDIUM:
			return new Double(configuration.mediumHostProbability);
		case LARGE:
			return new Double(configuration.largeHostProbability);
		default:
			throw new IllegalArgumentException("Invalid value for "
					+ HostCapacityEnum.class.getSimpleName() + " enumeration");

		}
	}

}
